/**
 * @author Francesco Rosso, matr 592057
 */
package pcd1112.model;

import java.util.ArrayList;
import java.util.Random;

import pcd1112.model.eccezioni.NodoNonPresenteException;
import pcd1112.model.eccezioni.NodoPresenteException;
import pcd1112.model.messaggi.MessaggioBroadcast;
import pcd1112.model.messaggi.MessaggioUnicast;

public class Ambiente {

	private final static Object syncObj = new Object();

	private static Ambiente istanza = null;
	private ArrayList<INodo> nodi = new ArrayList<INodo>();
	private boolean cloneFound = false;
	private boolean stopEverything = false;
	private boolean pauseEverything = false;

	private Ambiente() {
	}

	public static synchronized Ambiente getAmbiente() {
		if (istanza == null)
			istanza = new Ambiente();
		return istanza;
	}

	public void setCloneFound(boolean value) {
		this.cloneFound = value;
		setStopEverything(value);
	}

	public boolean isCloneFound() {
		return this.cloneFound;
	}

	/**
	 * Adds a node to the node list and worries about starting its thread
	 * 
	 * @param n
	 * @throws NodoPresenteException
	 *             if there already is a node in the specified coordinate
	 */
	public synchronized void aggiungiNodo(Nodo n) throws NodoPresenteException {
		try {
			getNodo(n.getPosizione());
			throw new NodoPresenteException();
		} catch (NodoNonPresenteException e) {

			if (Config.getConfig().getCurrentMode() == Config.Mode.teacherDefault) {
				// devo scorrere la lista dei nodi per trovare i vicini del
				// nodo corrente
				double raggio = Config.getConfig().getR();
				Coordinata corrente = n.getPosizione();
				for (int i = 0; i < getNodi().size(); i++) {
					Coordinata c = getNodi().get(i).getPosizione();
					if (corrente.compresaInArea(c, raggio)) {
						getNodi().get(i).aggiungiVicino(n);
						n.aggiungiVicino(getNodi().get(i));
					}
				}
			}

			new Thread(n).start();
			nodi.add(n);

		}
	}

	/**
	 * Return the placed in a specified coordinate
	 * 
	 * @param c
	 * @return
	 * @throws NodoNonPresenteException
	 *             if there is no node
	 */
	public INodo getNodo(Coordinata c) throws NodoNonPresenteException {
		for (int i = 0; i < nodi.size(); i++) {
			if (nodi.get(i).getPosizione().equals(c)) {
				return nodi.get(i);
			}
		}
		throw new NodoNonPresenteException();
	}

	/**
	 * Sends an unicast message to the node in a specified coordinate. If in the
	 * point specified in the coordinate there is no node, the message will be
	 * lost
	 * 
	 * @param m
	 * @param c
	 */
	public synchronized void inviaUnicast(MessaggioUnicast m, Coordinata c) {
		try {
			synchronized (syncObj) {
				while (pauseEverything)
					syncObj.wait();

				if (!isStopEverything()) {
					try {
						INodo n = getNodo(c);
						n.riceviMessaggio(m);
					} catch (NodoNonPresenteException e) {
						// System.out
						// .println("Non ho trovato il nodo specificato in coordinata X:"
						// + c.getX() + " Y:" + c.getY());
					}
				}
			}
		} catch (InterruptedException e1) {
			// TODO Auto-generated catch block
			// e1.printStackTrace();
		}
	}

	// prerequisito: il mittente del messaggio m mandato in Broadcast è
	// sempre il nodo che richiama il broadcast
	/**
	 * Send a message in the sender's broadcast
	 * 
	 * @param m
	 */
	public synchronized void inviaBroadcast(MessaggioBroadcast m) {
		try {
			synchronized (syncObj) {
				while (pauseEverything)
					syncObj.wait();
				if (!isStopEverything()) {
					// double xMitt = m.getPosMittente().getX();
					// double yMitt = m.getPosMittente().getY();
					double raggio;

					raggio = Config.getConfig().getR();

					for (int i = 0; i < nodi.size(); i++) {
						// controllo che il nodo sia compreso nel broadcast del
						// mittente
						// e che non si tratti del mittente stesso
						if (nodi.get(i).getPosizione()
								.compresaInArea(m.getPosMittente(), raggio)
								&& nodi.get(i).getPosizione() != m
										.getPosMittente()) {
							// System.out
							// .println("Ambiente: Invio messaggio broadcast al nodo"
							// + nodi.get(i).getIdNodo());
							nodi.get(i).riceviMessaggio(m);
						}
					}

				}
			}
		} catch (InterruptedException e1) {
			// TODO Auto-generated catch block
			// e1.printStackTrace();
		}
	}

	/**
	 * Starts the simulation worrying with the protocol speficied in the
	 * configuration file
	 */
	public void startSimulation() {
		if (Config.getConfig().getPROTO().equals("RED")) {
			Random generator = new Random();
			int rand = generator.nextInt();
			for (int i = 0; i < nodi.size(); i++) {
				nodi.get(i).startRED(rand);
			}
		} else {
			for (int i = 0; i < nodi.size(); i++) {
				nodi.get(i).startLSM();
			}
		}
	}

	/**
	 * Asks all the nodes to interrupt themselves
	 */
	private void interruptNodes() {
		for (int i = 0; i < nodi.size(); i++) {
			((Nodo) nodi.get(i)).interrupt();
		}
		AwaitStop.await(getNodi());
	}

	/**
	 * Resets the Ambiente object, requesting to and waiting for all the nodes
	 * to interrupt themselves
	 */
	public void reset() {
		resume();
		interruptNodes();
		this.nodi = new ArrayList<INodo>();
		this.setCloneFound(false);
	}

	/**
	 * Return the node list
	 * 
	 * @return
	 */
	public ArrayList<INodo> getNodi() {
		return nodi;
	}

	/**
	 * @return the stopEverything
	 */
	public boolean isStopEverything() {
		return stopEverything;
	}

	/**
	 * @param stopEverything
	 *            the stopEverything to set
	 */
	public void setStopEverything(boolean stopEverything) {
		this.stopEverything = stopEverything;
		if (stopEverything) {
			interruptNodes();
			resume();
		}
	}

	/**
	 * pause the current messages exchanges
	 */
	public void pause() {
		pauseEverything = true;
	}

	/**
	 * Resumes the messages exchanges
	 */
	public void resume() {
		synchronized (syncObj) {
			pauseEverything = false;
			syncObj.notifyAll();
		}
	}

	public boolean isPauseEverything() {
		return pauseEverything;
	}

}
